package com.unknow.first.dapp.admin.account.utils;

import com.unknow.first.dapp.admin.account.config.AddressAccountProperties;
import org.cloud.utils.SpringContextUtil;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.*;
import org.web3j.abi.datatypes.generated.Bytes32;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.contracts.eip20.generated.ERC20;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.ReadonlyTransactionManager;
import org.web3j.tx.gas.DefaultGasProvider;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Web3Utils {

    private List<String> rpcList;

    private void setRpcList(List<String> rpcList) {
        this.rpcList = rpcList;
    }

    private Web3Utils() {
        List<String> rpcUrls = SpringContextUtil.getBean(AddressAccountProperties.class).getRpcUrls();
        Assert.isTrue(!ObjectUtils.isEmpty(rpcUrls), "rpc url未配置");
        this.rpcList = rpcUrls;
    }

    public static Web3Utils single() {
        return Web3Utils.Holder.instance;
    }

    private static class Holder {

        private static Web3Utils instance = new Web3Utils();

        private Holder() {

        }
    }

    public Web3j web3j(){
        int randomIdx = new Random().nextInt(rpcList.size());
        return Web3j.build(new HttpService(rpcList.get(randomIdx)));
    }

    /**
     * 根据卖出数量（wei）获取买入数量
     *
     * @param web3j
     * @param router
     * @param sell
     * @param buy
     * @param sellValue
     * @return
     * @throws IOException
     * @dev 卖出买入数量都是wei
     */
    public BigInteger getAmountsOutForRouter(Web3j web3j, String router, DynamicArray<Address> routerPath, BigInteger sellValue)
        throws IOException {
        Function function = new Function("getAmountsOut", Arrays.asList(new Uint256(sellValue), routerPath),
            Collections.singletonList(new TypeReference<DynamicArray<Uint256>>() {
            }));
        String data = FunctionEncoder.encode(function);

        Transaction transaction = Transaction.createEthCallTransaction(Address.DEFAULT.getValue(), router, data);
        String response = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send().getValue();

        List<Type> decode = FunctionReturnDecoder.decode(response, function.getOutputParameters());
        List<Uint256> outAmounts = (((DynamicArray<Uint256>) decode.get(0))).getValue();
        return outAmounts.get(outAmounts.size() - 1).getValue();
    }

    public BigDecimal getAmountsOutForRouter(Web3j web3j, String router, DynamicArray<Address> routerPath, BigDecimal sellAmount)
        throws Exception {
        List<Address> routerPathList = routerPath.getValue();
        final ReadonlyTransactionManager transactionManager = new ReadonlyTransactionManager(web3j, Address.DEFAULT.getValue());
        ERC20 erc20Sell = ERC20.load(routerPathList.get(0).toString(), web3j, transactionManager, new DefaultGasProvider());
        ERC20 erc20Buy = ERC20.load(routerPathList.get(routerPathList.size() - 1).toString(), web3j, transactionManager,
            new DefaultGasProvider());
        BigInteger sellDecimals = erc20Sell.decimals().send();
        BigInteger buyDecimals = erc20Buy.decimals().send();
        BigInteger sellValue = sellAmount.multiply(BigDecimal.TEN.pow(sellDecimals.intValue())).toBigInteger();
        BigInteger buyValue = getAmountsOutForRouter(web3j, router, routerPath, sellValue);
        return new BigDecimal(buyValue).divide(BigDecimal.TEN.pow(buyDecimals.intValue()), 18, RoundingMode.DOWN);
    }


    /**
     * 根据卖出卖出数量获取买入数量
     *
     * @param web3j
     * @param router
     * @param sell
     * @param buy
     * @param sellAmount
     * @return
     * @throws Exception
     * @dev 卖出买入数量都是ether单位
     */
    public BigDecimal getAmountsOutForRouter(Web3j web3j, String router, String sell, String buy, BigDecimal sellAmount) throws Exception {
        return this.getAmountsOutForRouter(web3j, router, new DynamicArray<>(Address.class, new Address(sell), new Address(buy)),
            sellAmount);
    }

    public boolean isETHValidAddress(String input) {
        if (!input.startsWith("0x")) {
            return false;
        }
        String cleanHexInput = Numeric.cleanHexPrefix(input);
        try {
            Numeric.toBigIntNoPrefix(cleanHexInput);
        } catch (NumberFormatException e) {
            return false;
        }

        return cleanHexInput.length() == 40;
    }

    public Long getChainId(Web3j web3j){
        try {
            return web3j.ethChainId().send().getChainId().longValue();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 查询ETH提现订单状态
     * @param web3j
     * @param ETHWithdrawContract
     * @param orderNo
     * @return
     * @throws IOException
     */
    public boolean getETHWithdrawOrderStatus(Web3j web3j, String ETHWithdrawContract, String orderNo)
            throws IOException {
        Function function = new Function("withdrawOrderNo", Arrays.asList(new Bytes32(Numeric.hexStringToByteArray(orderNo))),
                Collections.singletonList(new TypeReference<Bool>() {
                }));
        String data = FunctionEncoder.encode(function);
        Transaction transaction = Transaction.createEthCallTransaction(Address.DEFAULT.getValue(), ETHWithdrawContract, data);
        String response = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send().getValue();

        List<Type> decode = FunctionReturnDecoder.decode(response, function.getOutputParameters());
        return ((Bool)decode.get(0)).getValue();
    }
}
